领域驱动学习-第四部分

战略设计原则即明确系统的概念核心:上下文、精炼和大型结构。

第14章 保持模型的完整性

模型最基本的要求是它应该保持内部一致,术语总具有相同的意义,并且不包含相互矛盾的规则:虽然我们很少明确地考虑这些要求。模型的内部一致性又叫统一(unification),这种情况下,每个术语都不会有模棱两可的意义,也不会有规则冲突。除非模型在逻辑上是一致的,否则它就没有意义。在理想世界中,我们可以得到涵盖整个企业领域的单一模型。这个模型将是统一的,没有任何相互矛盾或相互重叠的术语定义。每个有关领域的逻辑声明都是一致的。

但,大型系统开发并非如此理想。在整个企业系统中保持这种水平的统一是一件得不偿失的事情。在系统的各个不同部分中开发多个模型是很有必要的,但我们必须慎重地选择系统的哪些部分可以分开,以及它们之间是什么关系。我们需要用一些方法保持模型关键部分的高度统一。所有这些都不会自行发生,而且光有良好的意愿也没用的。它只有通过有意识的设计决策和建立特定过程才能实现。大型系统领域模型的完全统一既不可行,也不划算

14.1 模式:BOUNDED CONTEXT(限界上下文)

任何大型项目都会存在多个模型。而当基于不同模型的代码被组合到一起后,软件就会出现bug,变得不可靠和难以理解。团队成员之间的沟通变得混乱。人们往往弄不清楚一个模型不应该在哪个上下文中使用。

**因此:**明确地定义模型所应用的上下文。根据团队的组织,软件系统的各个部分的用法以及物理表现(代码和数据库模式等)来设置模型的边界。在这些边界中严格保持模型的一致性,而不要收到边界之外问题的干扰和混淆。

BUOUNDED CONTEXT不是MODULE。有时这两个概念易引起混淆,但它们是具有不同动机的不同模式。确实,当两组对象组成两个不同模型时,人们几乎总是把它们放在不同的MODULE中。这样做的确提供了不同的命名空间(对不同的CONTEXT很重要)和一些划分方法。但人们也会在一个模型中用MODULE来组织元素,它们不一定要表达划分CONTEXT的意图。MODULE在BOUNDED CONTEXT内部创建的独立命名空间实际上使人们很难发现意外产生的模型分裂。

14.2 模式:CONTINUOUS INTEGRATION(持续集成)

模型在开发过程中可能会被修改,极限编程(XP)非常适合维护涉及到一致性。

CONTINUOUS INTEGRATION指将一个上下文中所有的工作频繁合并在一起,并使他们保持一致,以便模型分裂时可以快速发现问题。

模型概念的集成;实现的集成

基本方法是对UBIQUITOUS LANGUAGE(通用语言)多加锤炼。

感悟:

首先就是需要定义通用的模型,合作开发时最好可以在进行模型的改动或者概念的改动误解时要及时沟通,变更后需要及时合并代码,通过自动化测试检测模型是否分裂。

14.3 模式:CONTEXT MAP(上下文整体关联图)

团队中不可能每个人都对不同BOUNDED CONTEXT的边界都清楚,如果有人做出修改,使上下文边界模糊。当不同上下文必须互相连接时,可能会互相重叠。

不同CONTEXT之间不允许代码重用,因此需要定义不同上下文之间的关系,并在项目中创建上下文的全局视图,以减少混乱。

CONTEXT MAP不拘泥于表现形式,但却需要在所有项目人员之间共享,并被他们理解。他必须为每个CONTEXT提供一个明确的名称,而且必须阐明联系点和他们的本质。

简单来说就是用画图的方式展示多个上下文之间的映射关系。

感悟:

两个context之间需要交互时可能需要理解对方模型中的一些概念,因此可以提供一个模型双向转换器(概念转换映射关系维护),由双方一起维护。

14.3.1 测试CONTEXT的边界

对各个BOUNDED CONTEXT的联系点的测试特别重要。这些测试有助于解决转换时所存在的 一些细微问题以及弥补边界沟通上存在的不足。

14.3.2 CONTEXT MAP的组织和文档化

  1. BOUNDED CONTEXT应该有名称,以便可以讨论它们。这些名称应该被添加到团队的 UBIQUITOUS LANGUAGE中

  2. 每个人都应该知道边界在哪里,而且应该能够分辨出任何代码段的CONTEXT,或任何情 况的CONTEXT。

14.4 BOUNDED CONTEXT之间的关系

下面介绍的这些模式涵盖了将两个模型关联起来的众多策略。把模型连接到一起之后,就能 够把整个企业笼括在内。这些模式有着双重目的,一是为成功地组织开发工作设定目标,二是为 描述现有组织提供术语。

14.5 模式:SHARED KERNEL(共享内核)

如果两个团队开发紧密相关的应用程序,如果各自定义模型,那个后续可能就需要花费大量精力在模型转换上,并且可能伴随频繁改动。

共享内核即从领域模型中选择出一个双方团队同意共享的子集,甚至包含该模型部分相关的代码子集或者数据库设计的子集。这部分明确的内容需要有单独的自动化测试,任何修改都应该双方团队商议并通过自动化测试。

举例:

1、电商场景下的商品管理上下文和订单管理上下文,无论是商品管理还是订单处理时都需要明确库存信息,确保下单时库存是足够的,因此可以将库存信息作为商品管理和订单管理的共享内核,这个共享内核在这两个上下文中共享,确保库存信息的一致。

14.6 CUSTOMER/SUPPLER DEVELOPMENT TEAM(客户/供应商)

两个系统明确是上下游关系,此时模型只存在单向转换,上游不需要关注下游模型。

下游团队相当于上游团队的客户,上游团队根据下游团队的需求来协商需要执行任务并留出预算。自动化测试时可以将下游测试添加至上游的测试套件中,使上游在做出修改时可以明确知道是否对下游团队产生副作用。

通俗理解就是下游相当于客户,明确自身需求后制定对应的业务规则等等,由供应商团队(上游团队)根据客户需求,完成对应代码的开发和测试工作。

这种模式更适合跨团队协作,分布式系统,多领域等,且上游团队很乐意为下游团队提供服务时使用。

14.7 模式:CONFORMIST(跟随者)

两个开发者团队存在上下游关系,如果上游系统没有动力满足下游需求,那么下游项目除非选择自力更生的方式解决问题否则项目将永远被搁置。

解决办法:1、自力更生,完全放弃对上游的依赖(SEPARATE WAY各行其道);2、必须依赖上游,且上游设计复杂下游无法直接使用,需要下游开发自己的模型,并承担开发转换层的责任(ANTICORRUPTION LAYER);3、依赖上游,且上游设计质量不是很差,风格兼容,则下游团队可以采用CONFORMIST模式遵从上游团队的模型。

该模式具体来说,就是一个限界上下文(子域)在某些方面遵循另一个限界上下文(主域)的规则和模型,以确保一致性和协同工作。

常见实现方式应该就是基于事件驱动实现,子域订阅了主域的事件变更,进而达成限界上下文之间的协作。

14.8模式:ANTICORRUPTION LAYER(防腐层)

上游团队既不合作且设计也无法使用时,这种情况就需要ANTICORRUPTION LAYER,承担起转换层的责任。

无论是新增系统与遗留系统集成,或是一个系统需要使用另一个系统的模型数据,这种都可能存在模型认知的gap,同时还会对新系统增加对原始数据解释的负担。

创建一个隔离层,以便根据客户自己的领域模型来为客户提供相关功能。该层通过另一个系统现有接口与其进行对话,而只需对那个系统做出很少的修改,甚至无需修改。

ANTICORRUPTION LAYER区别于系统通信,而是基于不同模型和协议之间转换概念对象和操作的机制。

通俗地讲,就是将两个限界上下文隔离开,明确边界并确保数据交互在不同上下文间清晰一致。

通常适用于与外界系统交互,用来隔离外界系统的一些模型和概念;或者不同限界上下文之间概念与模型不匹配时,也可以采用该模式确保数据的交互。

14.8.1 设计ANTICORRUPTION LAYER的接口

通常以SERVICE形式出现,偶尔采用ENTITY。

主要负责两个系统之间的语义转换,可以重新对另一个系统的行为进行抽象,按照与内部模型一致的方法把服务和信息提供给内部系统。

14.8.2 实现ANTICORRUPTION LAYER

将其可实现为FACADE、ADAPTER和转换器的组合,外加两个系统之间进行对话所需的通信和传输机制。

FACADE是子系统的一个可供替换的接口,可以简化客户访问,并使子系统更易于使用。

ADAPTER是一个包装器,它允许客户使用另外一种协议,这种协议可以是行为实现者不理解的协议。

转换器主要负责概念对象和数据的实际转换工作。

14.8.3 一个关于防御的故事

长城虽然不是不可逾越的屏障,但却明确了一个边界,使临近地区都规范有序。但长城的修建也是付出了极大的代价的。

隔离策略的益处必须平衡它产生的代价。任何集成都是有代价的,因此必须确保在真正需要的地方进行集成。

14.9 SEPARATE WAY(各行其道)

集成总是代价高昂,而有时收获却很小。

如果两个功能不需要互相调用,且其使用的对象也不需要交互,或者操作期间也不需要共享数据,那么就完全不需要集成。

申明一个与其他上下文毫无关联的BOUNDED CONTEXT,使开发人员能够在这个小范围内找到简单、专用的解决方案。

这个模式强调在不同上下文中建立清晰的边界,以便处理不同的业务规则、概念和模型。

14.10 模式:OPEN HOST SERVICE(开放主机协议)

当一个子系统必须与大量其他系统进行集成时,为每个集成都定制一个转换层可能会减慢团队的工作速度。需要维护的东西会越来越多,而且进行修改的时候担心的事情也会越来越多。

定义一个协议,把你的子系统作为一组SERVICE供其他系统访问。开放这个协议,以便所有 需要与你的子系统集成的人都可以使用它。当有新的集成需求时,就增强并扩展这个协议,但个别团队的特殊需求除外。满足这种特殊需求的方法是使用一次性的转换器来扩充协议,以便使共享协议简单且内聚。

通常使用场景是需要共享功能或者服务(多个上下文出现相似功能和服务时可以进行共享),避免重复代码(多个上下文出现重复代码时可以进行复用)。

一般我们可以将开放主机服务看做RPC的API,也可以是消息机制。

14.11 模式:PUBLISHED LANGUAGE(发布语言)

发布语言,在两个限界上下文之间翻译模型需要一种共享的公用的语言。发布语言通常和开放主机服务一起用。

这个模式强调在多个限界上下文(Bounded Contexts)之间共享通用的语言和概念,以确保不同上下文之间的交流和理解更加清晰和一致。

比较常见的应用方式应该就是多个上下文公用的通用枚举,例如HTTP的response code,OGV大仓的SeasonTypeEnum

14.12 "大象"的统一

”盲人摸象“:每个盲人对大象的认知都不同,可以认为每个盲人都为大象建立了一个模型,如果不需要统一意见,那么他们各执己见就行,如果需要交换信息,则需要对大象进行集成。

首先不同盲人对大象的建模如下:相当于四个独立的BOUNDED CONTEXT
盲人都不愿意接受别人的模型;
盲人意识到他们讨论的是一个整体,那么代表着他们各自的认知应该只是大象的一部分,所以他们需要确定各个部分是如何相连的:

模型集成第二步时去掉各个模型中偶然或者不正确的方面 ,并创建新的概念。模型尽可能做到缺少正确特性,也不能增加错误特性。

14.13 选择你的模型上下文策略

14.13.1 团队决策或更高层决策

由团队决定在哪里定义COUNDED CONTEXT,以及他们之间的关系,这些决策必须传达给整个团队,并被团队中的每个人理解,通常情况下也需要与外部团队达成一致。

管理层决策不一定实用,最终集成的收益可能会小于代价,因此可以提前评估并反馈给管理层,进而相应的措施减小代价。

14.13.2 置身上下文中

简而言之,自己干的啥自己知道,熟悉自身的context,了解交互系统,能够意识不会超出CONTEXT MAP的边界就行。

14.13.3 转换边界

首选较大的BOUNDED CONTEXT
 当用一个统一模型来处理更多任务时,用户任务之间的流动更顺畅。
 一个内聚模型比两个不同模型再加它们之间的映射更容易理解。
 两个模型之间的转换可能会很难(有时甚至是不可能的)。
 共享语言可以使团队沟通起来更清楚。
首选较小的BOUNDED CONTEXT
 开发人员之间的沟通开销减少了。
 由于团队和代码规模较小,CONTINUOUS INTEGRATION更容易了。
 较大的上下文要求更加通用的抽象模型,而掌握所需技巧的人员会出现短缺。 不同的模型可以满足一些特殊需求,或者是能够把一些特殊用户群的专门术语和UBIQUITOUS LANGUAGE的专门术语包括进来。

14.13.4 接受那些我们无法改变的实物:描述外部系统

识别出明显不在开发中系统的任何BOUNDED CONTEXT中的子系统,并把这些子系统与设计隔离开。

14.13.5 与外部系统的关系

SEPARATE WAY、CONFORMIST、ANTICORRUPTION LAYER,视情况选择不同模式

14.13.6 设计中的系统

首先声明BOUNDED CONTEXT,并应用CONTINUOUS INTEGRATION,以便保持它们的统一。

团队规模增大,BOUNDED CONTEXT增加,此时可以采用SEPARATE WAY,根据功能进行划分。存在单向依赖可以选择CUSTOMER/SUPPLIER

14.13.7 用不同模型满足特殊需要

同一业务不同小组可能拥有不同的专业术语,一般情况下是按需定制的。如果需要改变他们,则需要解决差异问题,而且最终效果可能并不如意。

你可能决定通过不同的BOUNDED CONTEXT来满足这些特殊需要,除了转换层的CONTINUOUS INTEGRATION以外,让模型采用SEPARATE WAY模式。UBIQUITOUS LANGUAGE的不同专用术语将围 绕这些模型以及它们所基于的行话来发展。如果两种专用术语有很多重叠之处,那么SHARED KERNEL模式就可以满足特殊化要求,同时又能把转换成本减至最小。

14.13.8 部署

不同策略选择会导致部署时的不同。

CUSTOMER/SUPPLIER就需要上下游协调共同发布,因为存在依赖关系;SEPARATE WAY模式可以使工作简单很多。

14.13.9 权衡

通过总结这些指导原则可知有很多统一或集成模型的策略。一般来说,我们需要在无缝功能 集成的益处和额外的协调和沟通工作之间做出权衡。还要在更独立的操作与更顺畅的沟通之间做 出权衡。更积极的统一需要对有关子系统的设计有更多控制。

14.13.10 当项目正在进行时

  1. 根据当前状况定义限界上下文。CONTEXT MAP必须反应团队的实际工作,而不是反映那个通过遵守以上描述的指导原则而得出的理想组织。

  2. 就是围绕当前组织结构来 加强团队的工作。在CONTEXT中加强CONTINUOUS INTEGRATION。把所有分散的转换代码重构到 ANTICORRUPTION LAYER中。命名现有的BOUNDED CONTEXT,并确保他们处于项目的UBIQUITOUS LANGUAGE中。

  3. 开始修改边界

14.14 转换

14.14.1 合并CONTEXT:SEPARATE WAY → SHARED KERNEL

  1. 评估初始情况,确认两个上下文确实需要统一
  2. 建立合并过程。确定代码共享方式以及模块应该采用哪种命名约定
  3. 选择某个小的子领域作为开始,它应该是两个上下文重复出现的子领域,但不是核心领域的一部分。
  4. 两个团队共同为子领域开发新的模型,模型需要识别同义词和映射尚未被翻译的术语,需要提供测试集
  5. 实现模型,确认细节,出现问题则从第3步重新开始
  6. 确保两个团队的人员都承担与新的共享内核集成的任务
  7. 清除不需要的翻译

后续迭代可以通过重复3-7来共享更多内容

14.14.2 合并CONTEXT:SHARED KERNEL → CONTINUOUS INTEGRATION

  1. 确保每个团队都已经建立了CONTINUOUS INTEGRATION所需的所有过程(共享代码所有权、 频繁集成等)

  2. 团队成员在团队之间流动。这样可以形成一大批同时理解两个模型的人员,并且可以把 两个团队的人员联系起来。

  3. 澄清每个模型的精髓,即提炼模型

  4. 将核心领域合并到SHARED KERNEL中,可能需要几次更迭。

  5. 随着SHARED KERNEL的增加,提高集成频率到每天一次

  6. 当SHARED KERNEL逐渐把先前两个BOUNDED CONTEXT的所有内容都包括进来的时候,你 会发现要么形成了一个大的团队,要么形成了两个较小的团队,这两个较小的团队共享一个 CONTINUOUS INTEGRATION的代码库,而且团队成员可以经常在两个团队之间来回流动。

    2

14.14.3 逐步淘汰遗留系统

  1. 确认遗留系统的那个功能可以在一次迭代中被添加到某个新系统中
  2. 确定需要在ANTICORRUPTION LAYER中添加的功能
  3. 实现
  4. 部署
  5. 找出ANTICORRUPTION LAYER中不必要的部分,并移除
  6. 考虑删除遗留系统中目前未被使的模块

14.14.4 OPEN HOST SERVICE → PUBLISHED LANGUAGE

  1. 如果有一种行业标准语言可用,则尽可能评估并使用它。

  2. 如果没有标准语言或预先公开发布的语言,则完善作为HOST的系统的CORE DOMAIN

  3. 使用CORE DOMAIN作为交换语言的基础,尽可能使用像XML这样的标准交互范式

  4. 向所有参与协作的各方发布新语言

  5. 如果涉及新的系统架构,那么也要发布它

  6. 为每个协作系统构建转换层

  7. 切换

第十五章 精炼

领域模型的战略精炼包括以下部分:
(1) 帮助所有团队成员掌握系统的总体设计以及各部分如何协调工作;
(2) 找到一个具有适度规模的核心模型并把它添加到通用语言中,从而促进沟通; (3) 指导重构;
(4) 专注于模型中最有价值的那部分;
(5) 指导外包、现成组件的使用以及任务委派。

15.1 模式:CORE DOMAIN

为了 使领域模型成为有价值的资产,必须整齐地梳理出模型的真正核心,并完全根据这个核心来创建 应用程序的功能。

提炼模型,将最有价值和最专业的概念凸显出来,并开发确保实现系统蓝图的深层模型和柔性设计。

核心领域需要通过反复更迭确认。

它指的是一个领域模型中最核心、最关键的部分,涉及到业务的核心价值和不可替代的业务规则。

15.2 精炼的逐步提升

应用以下提炼技术:

15.3 模式:GENERIC SUBDOMAIN(通用子域)

通用子域可能是系統中一些通用元素, 与主要目标无关的辅助功能, 但对系統的功能却是不可或缺的; 它所抽离出来概念也是与其他商业应用同样需要的概念, 但别让它们包含任何的专业领域知识。

对于通用子域的开发可采用现成的解决方案或公开的模型。

一个子域具有通用性,并不代表它的代码具有通用性,通用子域主要是满足某些特地需求,不应该去关注它的代码重用性,尽可能的将精力投入在核心领域。

应避免为了可重用性而设计,但必须严格遵守通用概念的思想。

项目面临的风险来自两方面:(1)技术风险(2)领域建模风险

适用场景:

  1. 通用功能: 当业务中存在一些通用的功能,不属于核心业务领域,但多个领域都可以共享时,可以将这些功能放置在一个通用子域中。

  2. 解耦业务和通用功能: 通过将通用功能从核心业务领域中分离,可以减少核心领域的复杂性,将业务逻辑和通用逻辑解耦,使核心领域更专注于业务价值。

  3. 可维护性和扩展性: 将通用功能抽离到通用子域中可以提高代码的可维护性和可扩展性,使代码更加清晰和易于管理。

15.4 DOMAIN VISION STATEMENT(领域愿景说明)

以一页纸的篇幅简短描述核心领域将会创造的价值,也就是“价值主张”。说明领域模型是如何实现和均衡各方利益的。

领域愿景说明可以作为一个指南,帮助开发团队在精炼模型和代码的过程中保持统一的方向。

  1. 愿景: 对领域的长远目标和愿景的陈述,包括业务的核心价值和战略方向。

  2. 目标: 具体的短期和长期目标,涵盖了业务的关键成果和成就。

  3. 价值主张: 阐述了领域对客户、用户或利益相关者的价值,以及业务的独特之处。

  4. 核心价值: 突出了领域中最重要、最核心的业务价值,以及为什么这些价值对业务至关重要。

  5. 关键成功因素: 强调了实现领域愿景和目标所必需的关键成功因素。

  6. 业务挑战: 指出可能会影响领域愿景实现的主要业务挑战。

  7. 战略重点: 描述了在实现愿景和目标过程中的战略重点和重要步骤。

15.5 HIGHLIGHT CORE(突出核心)

为了确保团队每个人对核心领域有更全面性的了解,因此核心领域应该显而易见。

精炼文档:编写一个非常简短的文档(3~7页,每页内容不必太多),用于描述CORE DOMAIN以及CORE 元素之间的主要交互过程。

标明CORE:把模型的主要存储库中的CORE DOMAIN标记出来,不用特意去阐明其角色。使开发人员很容 易就知道什么在核心内,什么在核心外。

如果精炼文档概括了CORE DOMAIN的核心元素,那么它就可以作为一个指示器——用以指示模型改变的重要程度。 当模型或代码的修改影响到精炼文档时,需要与团队其他成员一起协商。

15.6 COHESIVE MECHANISM(内聚机制)

模型中的概念用”做什么(what)“来解释,而不是”怎么做(how)“

把概念上的COHESIVE MECHANISM(内聚机制)分离到一个单独的轻量级框架中。要特别注意公式或那些有完备文档的算法。用一个INTENTION-REVEALING INTERFACE来暴露这个框架的功 能。现在,领域中的其他元素就可以只专注于如何表达问题(做什么)了,而把解决方案的复杂 细节(如何做)转移给了框架。

通用子域VS内聚机制所承担责任的不同在于:

通用子域:团队应如何看待领域中一些的方面,这点与核心领域无异,但它没有核心领域这么专业化。

内聚机制:不是用来表示领域,而是负责解决一些由描述性模型提出的棘手问题。

总而言之,模型提出问题,内聚机制解决问题。

15.7 通过精炼得到声明式风格

精炼的价值在于能看到正在做的工作,而不受不相干的内容干扰,直指系统本意。

15.8 SEGREGATED CORE(隔离核心)

重构模型,将核心概念与辅助分离开来,加强核心的内聚力,同时减少核心与其他的代码的联系。

分离隔离核心步骤如下:

  1. 确定一个核心子域
  2. 把相关类转移至新的模块,并为模块指定一个与概念相符的名称
  3. 重构代码,将无法直接表示核心概念的数据或方法分离出来,尽量把他们放在概念相关的模块中,但别花太多时间,把工作重点放在精炼核心子域上,核心子域对其他的模块引用必须明确突出。
  4. 对得到的隔离核心重构,湿气重关系与交互作用简单易懂,同时减少与其他模块的关系。
  5. 对其他核心子域重复以上步骤。

对核心进行隔离有时会破坏原本的非核心类之间的紧密联系,导致其变得模糊甚至复杂,但它可以是核心领域更加清晰,更加容易处理。

由于核心领域和设计中的其他部分一样是不断严谨的,团队成员对那些是核心元素,哪些是辅助元素也会产生新的认知,而这些认识应该反馈到核心领域,使他们的定义得到进一步精确化。

团队内部沟通必须非常高效,保证每个人对核心认识都是一致的。

15.9 ABSTRACT CORE(抽象核心)

当不同MODULE的子领域之间有大量交互时,要么需要在MODULE之间创建很多引用,这在 很大程度上抵消了划分模块的价值;要么就必须间接地实现这些交互,而后者会使模型变得晦涩难懂。

把模型中最基本的概念识别出来,并分离到不同的类、抽象类或接口中。设计这个抽象模型, 使之能够表达出重要组件之间的大部分交互。把这个完整的抽象模型放到它自己的MODULE中, 而专用的、详细的实现类则留在由子领域定义的MODULE中。

15.10 深层模型提炼

精炼的目标是把模型设计得更明显,使我们可以用模型简单地把领域表示出来。深层模型把 领域中最本质的方面精炼成一些简单的元素,使我们可以把这些元素组合起来解决应用程序中的 重要问题。

15.11 选择重构目标

”哪儿痛治哪儿’:需要观察是否设计核心领域或核心与支持元素的关系,如果涉及,首先修复核心;

“自由重构”:首先需要集中精力提炼核心领域,完善对CORE的分离,并且把支持性的子领域提炼成通用子领域。

第16章 大型结构

在一个大的系统中,如果因为缺少一种全局性的原则而使人们无法根据元素在模式(这些模 式被应用于整个设计)中的角色来解释这些元素,那么开发人员就会陷入“只见树木,不见森林” 的境地。

设计一种应用于整个系统的规则(或角色和关系)模式,使人们可以通过它在一定程度上了 解各个部分在整体中所处的位臵(即使是在不知道各个部分的详细职责的情况下)。

16.1 模式:EVOLVING ORDER(演化有序)

一个没有任何规则的随意设计会产生一些无法理解整体含义且很难维护的系统。但架构中早 期的设计假设又会使项目变得束手束脚,而且会极大地限制应用程序中某些特定部分的开发人员 /设计人员的能力。很快,开发人员就会为适应结构而不得不在应用程序的开发上委曲求全,要么就是完全推翻架构而又回到没有协调的开发老路上来。

因此:让这种概念上的大型结构随着应用程序一起演变,甚至可以变成一种完全不同的结构风格。 不要依此过分限制详细的设计和模型决策,这些决策和模型决策必须在掌握了详细知识之后才能 确定。

与CONTEXT MAP 不同的是,大型结构是可选的。当发现一种大型结构可以明显使系统变得更清晰, 而又没有对模型开发施加一些不自然的约束时,就应该采用这种结构。使用不合适的结构还不如 不使用它,因此最好不要为了追求设计的完整性而勉强去使用一种结构,而应该找到尽可能精简 的方式解决所出现问题。要记住宁缺勿滥的原则。

16.2 模式:SYSTEM METAPHOR(系统隐喻)

软件设计往往非常抽象且难于掌握。开发人员和用户都需要一些切实可行的方式来理解系 统,并共享系统的一个整体视图。

SYSTEM METAPHOR(系统隐喻)是一种松散的、易于理解的大型结构,它与对象范式是协调的。由于系统隐喻只是对领域的一种类比,因此不同模型可以用近似的方式来与它关联,这使得人们能 够在多个BOUNDED CONTEXT中使用系统隐喻,从而有助于协调各个BOUNDED CONTEXT之间的工作。

当系统的一个具体类比正好符合团队成员对系统的想象,并且能够引导他们向着一个有用的 方向进行思考时,就应该把这个类比用作一种大型结构。围绕这个隐喻来组织设计,并把它吸收 到UBIQUITOUS LANGUAGE中。SYSTEM METAPHOR应该既能促进系统的交流,又能指导系统的开 发。它可以增加系统不同部分之间的一致性,甚至可以跨越不同的BOUNDED CONTEXT。但所有 隐喻都不是完全精确的,因此应不断检查隐喻是否过度或不恰当,当发现它起到妨碍作用时,要随时准备放弃它。

SYSTEM METAPHOR的角色可以由UBIQUITOUS LANGUAGE来承担

16.3 模式:RESPONSIBILITY LAYER(职责分层)

如果每个对象的职责都是人为分配的,将没有统一的指导原则和一致性,也无法把领域作为一个整体来处理。为了保持大型模型的一致,有必要在职责分配上实施一定的结构化控制。

**因此:**注意观察模型中的概念依赖性,以及领域中不同部分的变化频率和变化的原因。如果在领域中发现了自然的层次结构,就把它们转换为宽泛的抽象职责。这些职责应该描述系统的高层目的和设计。对模型进行重构,使得每个领域对象,AGGREGATE和MODULE的职责都清晰地位于一个职责层当中。

想要找到一种适当的RESPONSIBILITY LAYER或大比例结构,需要理解问题领域并反复进行实验。如果遵循EVOLVING ORDER,那么最初的起点并不是十分重要,尽管差劲的选择确实会加大工作量。结构可能最后演变得面目全非。因此,下面将给出一些指导方针,无论是刚开始选择一种结构,还是对已有结构进行转换,这些指导方针都适用。

当对层进行删除、合并、拆分和重新定义等操作时,应寻找并保留一下一些有用的特征:

□ 场景描述。层应该能够表达出领域的基本实现或优先级选择一种大比例结构与其说是一种技术决策,不如说是一种业务建模决策。

□ 概念依赖性。“较高”层概念的意义应该依赖“较低”层,而低层概念的意义应该独立于较高层。

□ **CONCEPTUAL CONTOUR。**如果不同层的对象必须具有不同的变化频率或原因,那么层应该能够容许它们之间的变化。

□ **潜能层。**我们能够做什么?潜能层不关心我们打算做什么,而关心能够做什么。如企业的资源(包括人力资源)以及这些资源的组织方式是潜能层的核心。

□ **作业层。**我们正在做什么?我们利用这些潜能做了什么事情?像潜能层一样,这个层也应该反映出现实情况,而不是我们设想的状况。如我们希望在这个层中看到自己的工作和活动:我们正在销售什么,而不是能够销售什么。通常来说,作业层对象可以引用潜能层对象,它甚至可以由潜能层对象组成,但潜能层对象不应该引用作业层对象。

□ **决策支持层。**应该采取什么行动或制定什么策略?这个层是用来作出分析和制定决策的。它根据来自较低层(如潜能层或作业层)的信息进行分析。决策支持软件可以利用历史信息来主动寻找适用于当前和未来作业的机会

□ **策略层。**规则和目标是什么?规则和目标主要是被动的,但它们约束着其他层的行为。这些交互的设计是一个微妙的问题。有时策略会作为一个参数传递给较低层的方法。有时会使用STRATEGY模式。策略层与决策支持层能够进行很好的协作,决策支持层提供了用于搜索策略层所设定的目标的方式,这些目标又受到策略层设定的规则约束。

□ **承诺层。**我们承诺了什么?这个层具有策略层的性质,因为他表述了一些指导未来运营的目标;但它也有作业层的性质,因为承诺是作为后续因为活动的一部分而出现和变化的。

虽然这5个层对很多企业系统都适用,但并不是所有领域的主要概念都涵盖在这5个层中。有些情况下,在设计中生硬地套用这种形式反而会起反作用,而使用一组更自然的RESPONSIBILITY LAYER会更有效。

我们需要对分层结构进行调整和实验,但一定要使分层系统保持简单,如果层数超过4或5,就比较难处理了。层数越多将无法有效地描述领域,而且本来要使用大比例结构解决的复杂性问题又会以一种新的方式出现。我们必须对大比例结构进行严格的精简。

如果一个领域与上述讨论毫无关系,所有的分层可能都必须从头开始。最后,我们必须根据直觉选择一个起点,然后通过EVOLVING ORDER来改进它。

16.4 模式:KNOWLEDGE LEVEL(知识级别)

如果在一个应用程序中,ENTITY的角色和它们之间的关系在不同的情况下有很大变化,那么复杂性会显著增加。在这种情况下,无论是一般的模型还是高度定制的模型,都无法满足用户的需求。为了兼顾各种不同的情形,对象需要引用其他的类型,或者需要具备一些在不同情况下包括不同使用方式的属性。具有相同数据和行为的类可能会大量增加,而这些类的唯一作用只是为了满足不同的组装规则。

**因此:**创建一组不同的对象,用它们来描述和约束基本模型的结构和行为。把这些对象分为两个“级别”,一个是非常具体的级别,另一个级别则提供了一些可供用户或超级用户定制的规则和知识。

如果得到合理的运用,KNOWLEDGE LEVEL能够解决一些其他方式很难解决的问题。如果系统中某些部分的定制非常关键,而要是不提供定制能力就会破坏掉整个设计,这时就可以利用知识级别来解决这一问题。

像其他大比例结构一样,KNOWLEDGE LEVEL也不是必须要使用的。没有它,对象照样能工作,而且团队可能仍然能够认识到他们需要将Employee与Payroll分离。当项目进行到某个时刻,这种结构看起来已经没什么用了,那么就可以放弃它。

乍看上去,KNOWLEDGE LEVEL像是RESPONSIBILITY LAYER(特别是策略层)的一个特例,但它并不是。首先,KNOWLEDGE LEVEL两个级别之间的依赖是双向的,而RESPONSIBILITY LAYER在层次结构中,较低的层不依赖于较高的层。实际上,RESPONSIBILITY LAYER可以与其他大部分的大比例结构共存,它提供了另一种用来组织模型的维度。

16.5 模式:PLUGGABLE COMPONENT FRAMEWORK(可插入式组件框架)

在深入理解和反复精炼基础上得到的成熟模型中,会出现很多机会。通常只有在同一个领域中实现了多个应用程序之后,才有机会使用PLUGGABLE COMPONENT FRAMEWORK(可插入式组件框架)。

当很多应用程序需要进行相互操作时,如果应用程序都基于相同的一些抽象,但它们是独立设计的,那么在多个BOUNDED CONTEXT之间的转换会限制它们的集成。各个团队之间如果不能紧密地协作,就无法形成一个SHARED KERNEL。重复和分裂将会增加开发和安装的成本,而且互操作会变得很难实现。

**因此:**从接口和交互中提炼一个ABSTRACT CORE,并创建一个框架,这个框架要允许这些接口各种不同实现被自由替换。同样,无论是什么应用程序,只要它严格地通过ABSTRACT CORE的接口进行操作,那么就可以允许它使用这些组件。

PLUGGABLE COMPONENT FRAMEWORK也有几个缺点:

总结:

1、最小化:

控制成本的一个关键是保持一种简单、轻量级的结构。不要试图使结构面面俱到。只需解决 最主要的问题即可,其他问题可以留到后面一个一个地解决。

开始最好选择一种松散的结构,如SYSTEM METAPHOR或几个RESPONSIBILITY LAYER。不管怎样,一种最小化的松散结构可以起到轻量级的指导作用,它有助于避免混乱。

2、沟通和自律

整个团队在新的开发和重构中必须遵守结构。要做到这一点,整个团队必须理解这种结构。 必须把术语和关系纳入到UBIQUITOUS LANGUAGE中。

在大多数团队中,仅仅通过沟通是不足以保证在系统中采用一致的大比例结构的。至关重要 的一点是要把它合并到项目的通用语言中,并让每个人都严格地使用UBIQUITOUS LANGUAGE。

3、通过重构得到柔性设计

4、通过精炼可以减轻负担

第17章 领域驱动设计的综合应用

17.1 把大型结构与BOUNDED CONTEXT结合起来使用

17.2 将大型结构与精炼结合起来使用

17.3 首先评估

17.4 由谁制定策略

17.4.1 从应用程序开发自动得出的结构

17.4.2 以客户为中心的架构团队

17.5 制定战略设计决策的6个要点

1、决策必须传达整个团队;

2、决策过程必须收集反馈意见;

3、计划必须允许演变;

4、架构团队不必把所有最好、最聪明的人员都吸收进来;

5、战略设计需要遵守简约和谦逊的原则;

6、对象的职责要专一,而开发人员应该是多面手。

17.5.1 技术框架同样如此

17.5.2 注意总体规划